/**   
 * Copyright 2011 COMERE Harold
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package etrl.camera;

import etrl.core.Group;
import etrl.core.NodeVisitor;
import etrl.core.TransformData;
import etrl.vecmath.Matrix4f;
import etrl.vecmath.Quat4f;
import etrl.vecmath.Vector3f;

/**
 * \class Camera
 * \brief Object storing information about point of view position / orientation, view volume and projection.
 */
public class Camera extends Group
{
	/**
	 * Camera projection matrix
	 */
	private Matrix4f _projectionMatrix;
	
	/**
	 * Camera view matrix
	 */
	private Matrix4f _viewMatrix;

	/**
	 * \brief Transform storing position and orientation
	 */
	private TransformData _transform;

	/**
	 * Backup of znear plane value
	 */
	private float _znear;

	/**
	 * Backup of zfar plane value
	 */
	private float _zfar;

	/**
	 * \brief Gets the camera projection matrix.
	 * @return The projection matrix.
	 */
	public Matrix4f getProjectionMatrix()
	{
		Matrix4f mat = new Matrix4f(_projectionMatrix);
		return mat;
	}

	/**
	 * \brief Applies a translation to the camera on a custom vector.
	 * @param[in] translationVector Translation vector to apply.
	 */
	public void translate(Vector3f translationVector)
	{
		_transform.translate(translationVector);
	}

	/**
	 * \brief Computes the current view matrix, inverted.
	 * @return Inverted view matrix.
	 */
	public Matrix4f getInverseViewMatrix()
	{
		_viewMatrix = _transform.toMat4();
		_viewMatrix.invert();
		return _viewMatrix;
	}

	/**
	 * \brief Computes the current view matrix.
	 * @return Camera view matrix.
	 */
	public Matrix4f getViewMatrix()
	{
		return _transform.toMat4();
	}

	/**
	 * \brief Initializes default position and orientation
	 */
	public Camera()
	{
		_transform = new TransformData();
	}

	/**
	 * \brief Sets the projection of the camera as perspective.
	 * 
	 * Note that the generated is already transposed to match with glsl row-major matrix definition.
	 * 
	 * @param[in] fovy
	 *            Opening angle
	 * @param[in] aspectRatio
	 *            Ratio between screen width and screen height
	 * @param[in] znear
	 *            Depth of the near plane
	 * @param[in] zfar
	 *            Depth of the far plane
	 */
	public void setProjectionAsPerspective(float fovy, float aspectRatio, float znear, float zfar)
	{
		_znear = znear;
		_zfar = zfar;

		float top = znear * (float) Math.tan(fovy * Math.PI / 360.f);
		float bottom = -top;
		float left = bottom * aspectRatio;
		float right = top * aspectRatio;

		float X = 2 * znear / (right - left);
		float Y = 2 * znear / (top - bottom);
		float A = (right + left) / (right - left);
		float B = (top + bottom) / (top - bottom);
		float C = -(zfar + znear) / (zfar - znear);
		float D = -2 * zfar * znear / (zfar - znear);

		_projectionMatrix = new Matrix4f(X, 0.0f, A, 0.0f, 0.0f, Y, B, 0.0f, 0.0f, 0.0f, C, -1.0f, 0.0f, 0.0f, D, 0.0f);
	}

	/**
	 * \brief Sets the projection matrix as orthogonal for 2D projection.
	 * 
	 * Note that the generated is already transposed to match with glsl row-major matrix definition.
	 * 
	 * @param[in] xmin
	 *            Left plane position on X axis
	 * @param[in] xmax
	 *            Right plane position on X axis
	 * @param[in] ymin
	 *            Bottom plane position on Y axis
	 * @param[in] ymax
	 *            Top plane position on Y axis
	 * @param[in] zmin
	 *            Near plane position on Z axis
	 * @param[in] zmax
	 *            Far plane position on Z axis
	 */
	public void setProjectionAsOrtho(float xmin, float xmax, float ymin, float ymax, float zmin, float zmax)
	{
		_projectionMatrix = new Matrix4f(2.f / (xmax - xmin), 0.f, 0.f, 0.f, 0.f, 2.f / (ymax - ymin), 0.f, 0.f, 0.f, 0.f, -2.f / (zmax - zmin), 0.f, -(xmax + xmin) / (xmax - xmin), -(ymax + ymin) / (ymax - ymin), -(zmax + zmin) / (zmax - zmin), 1.f);
	}

	/**
	 * \brief Gets the position of the near plane on Z axis.
	 * @return znear value
	 */
	public float getZNear()
	{
		return _znear;
	}

	/**
	 * \brief Gets the position of the far plane on Z axis.
	 * @return zfar value
	 */
	public float getZFar()
	{
		return _zfar;
	}

	/**
	 * \brief Applies a custom rotation to the camera
	 * @param[in] rotationAxis Axis of the rotation
	 * @param[in] rotationAngle Angle of the rotation in degrees
	 * @param[in] local If true, the axis rotation is defined in camera local space, else the axis is defined in world space
	 */
	public void rotate(Vector3f rotationAxis, float rotationAngle, boolean local)
	{
		_transform.applyRotation(rotationAxis, rotationAngle, local);
	}

	/**
	 * \brief Applies a translation to the camera on its local Z axis.
	 * @param[in] multiplier Multiplier applied on the translation step, the final translation length will be 1. * multiplier.
	 */
	public void translateOnZAxis(float multiplier)
	{
		_transform.translateOnZAxis(multiplier);
	}

	/**
	 * \brief Applies a translation to the camera on its local X axis.
	 * @param[in] multiplier Multiplier applied on the translation step, the final translation length will be 1. * multiplier.
	 */
	public void translateOnXAxis(float multiplier)
	{
		_transform.translateOnXAxis(multiplier);
	}

	/**
	 * \brief Applies a translation to the camera on its local Y axis.
	 * @param[in] multiplier Multiplier applied on the translation step, the final translation length will be 1. * multiplier.
	 */
	public void translateOnYAxis(float multiplier)
	{
		_transform.translateOnYAxis(multiplier);
	}

	/**
	 * \brief Defines a new position of the camera.
	 * @param[in] newPosition Position to set.
	 */
	public void setPosition(Vector3f newPosition)
	{
		_transform.setPosition(newPosition);
	}

	/**
	 * \brief Defines a new orientation of the camera.
	 * @param[in] newOrientation Orientation to set.
	 */
	public void setOrientation(Quat4f newOrientation)
	{
		_transform.setOrientation(newOrientation);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void accept(NodeVisitor nodeVisitor)
	{
		nodeVisitor.visit(this);
	}
}
